package gx

import (
	"archive/zip"
	"bufio"
	"errors"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)

const OSUnknown = 0
const OSFile = 1
const OSDir = 2

type OSPath struct {
	path string
}

func Path(path string) OSPath {
	return OSPath{path: path}
}

func (p OSPath) String() string {
	return p.path
}

func (p OSPath) FileInfo() (os.FileInfo, error) {
	return os.Stat(p.path)
}

func (p OSPath) FileType() (string, error) {
	fi, err := os.Lstat(p.path)
	if err != nil {
		return "", err
	}
	mode := fi.Mode()
	switch {
	case mode.IsRegular():
		return "regular file", nil
	case mode&os.ModeSymlink != 0:
		return "symbolic link", nil
	case mode&os.ModeDevice != 0:
		return "device file", nil
	case mode&os.ModeDir != 0:
		return "directory", nil
	case mode&os.ModeSocket != 0:
		return "socket", nil
	case mode&os.ModeNamedPipe != 0:
		return "named pipe", nil
	case mode&os.ModeCharDevice != 0:
		return "character device", nil
	case mode&os.ModeIrregular != 0:
		return "irregular file", nil
	default:
		return "unknown file type", nil
	}
}

func (p OSPath) IsFile() bool {
	fi, err := os.Stat(p.path)
	if err == nil {
		return fi.Mode().IsRegular()
	}
	return false
}

func (p OSPath) IsDir() bool {
	fi, err := os.Stat(p.path)
	if err == nil {
		return fi.IsDir()
	}
	return false
}

func (p OSPath) IsSymlink() bool {
	fi, err := os.Lstat(p.path)
	if err == nil {
		return fi.Mode()&os.ModeSymlink == os.ModeSymlink
	}
	return false
}

// 判断是否存在

func (p OSPath) Exists() bool {
	_, err := os.Stat(p.path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func (p OSPath) Mkdir(perm fs.FileMode) error {
	exists := p.Exists()
	if !exists {
		if perm == 0 {
			perm = os.ModePerm
		}
		if err := os.Mkdir(p.path, perm); err != nil {
			return err
		}
		return nil
	}
	return nil
}

func (p OSPath) Remove() error {
	return os.Remove(p.path)
}

// RemoveAll removes path and any children it contains.
// It removes everything it can but returns the first error
// it encounters. If the path does not exist, RemoveAll
// returns nil (no error).
// If there is an error, it will be of type *PathError.
func (p OSPath) RemoveAll() error {
	return os.RemoveAll(p.path)
}

func (p OSPath) Join(paths ...string) OSPath {
	return Path(filepath.Join(append([]string{p.path}, paths...)...))
}

func (p OSPath) HasPrefix(prefix ...string) bool {
	for _, s := range prefix {
		if strings.HasPrefix(p.path, s) {
			return true
		}
	}
	return false
}

func (p OSPath) HasSuffix(suffix ...string) bool {
	for _, s := range suffix {
		if strings.HasSuffix(p.path, s) {
			return true
		}
	}
	return false
}

func (p OSPath) Open() (fs.File, error) {
	if !p.IsFile() {
		return nil, errors.New("not file")
	}
	return os.Open(p.path)
}

func (p OSPath) ReadAll() ([]byte, error) {
	f, err := p.Open()
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return io.ReadAll(f)
}

func (p OSPath) ReadString() (string, error) {
	b, err := p.ReadAll()
	if err != nil {
		return "", err
	}
	return string(b), nil
}

// 按行读取文件
func (p OSPath) ReadByteLine(fn func(bytes []byte, err error)) error {
	f, err := p.Open()
	if err != nil {
		return err
	}
	defer f.Close()
	reader := bufio.NewReader(f)
	for {
		lineBytes, err := reader.ReadBytes('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}
		fn(lineBytes, nil)
	}
	return nil
}

// 按行读取文件
func (p OSPath) ReadLine(fn func(line string, err error)) error {
	f, err := p.Open()
	if err != nil {
		return err
	}
	defer f.Close()
	reader := bufio.NewReader(f)
	for {
		line, err := reader.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			return err
		}
		fn(line, nil)
	}
	return nil
}

// see filepath.Walk
func (p OSPath) Walk(fn func(path OSPath, info fs.FileInfo, err error) error) error {
	return filepath.Walk(p.path, func(path string, info fs.FileInfo, err error) error {
		return fn(Path(path), info, err)
	})
}

// zip files
func (p OSPath) Zip(output string) error {
	zipfile, err := os.Create(output)
	if err != nil {
		return err
	}
	defer zipfile.Close()

	archive := zip.NewWriter(zipfile)
	defer archive.Close()

	parentPath := filepath.ToSlash(p.Join("..").path)
	return p.Walk(func(path OSPath, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}

		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}
		header.Name = filepath.ToSlash(path.String())
		header.Name = strings.TrimPrefix(header.Name, parentPath)
		header.Name = strings.TrimPrefix(header.Name, "/")
		if info.IsDir() {
			header.Name += "/"
		} else {
			header.Method = zip.Deflate
		}
		writer, err := archive.CreateHeader(header)
		if err != nil {
			return err
		}
		if !info.IsDir() {
			file, err := os.Open(path.String())
			if err != nil {
				return err
			}
			defer file.Close()
			if _, err = io.Copy(writer, file); err != nil {
				return err
			}
		}
		return err
	})
}

// 参考filepath.Glob
func (p OSPath) Glob(pattern string) ([]string, error) {
	return filepath.Glob(p.Join(pattern).String())
}
